using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Collections.Generic;

namespace Mbs.Services
{
    [Serializable]
    public class ClassLoader : MarshalByRefObject, IClassLoader
    {
        private List<Assembly> cache = new List<Assembly>();

        public ClassLoader()
        {

        }

        public bool IsDefault
        {
            get { return true; }
        }

        public IServiceLocator Locator
        {
            get
            {
                return ServiceLocator.Root;
            }
        }

        public Assembly[] GetAssemblies()
        {
            return cache.ToArray();
        }

        public Assembly LoadAssembly(string assemblyFile)
        {
            try
            {
                var asm = Assembly.LoadFrom(assemblyFile);
                if (asm != null)
                {
                    if (!cache.Contains(asm))
                        cache.Add(asm);

                    var dependancies = asm.GetReferencedAssemblies();
                    foreach (var item in dependancies)
                    {
                        var depAsm = Assembly.Load(item);
                        if (!cache.Contains(depAsm))
                            cache.Add(depAsm);
                    }
                }
                return asm;
            }
            catch (Exception ex)
            {
                ex.RaiseException();
                return null;
            }
        }

        public void LoadAssemblies(string directory)
        {
            LoadAssemblies(directory, "*.dll", false);
        }

        public void LoadAssemblies(string directory, bool includeChildDirectory)
        {
            LoadAssemblies(directory, "*.dll", includeChildDirectory);
        }

        public void LoadAssemblies(string directory,string filter, bool includeChildDirectory)
        {
            if (string.IsNullOrEmpty(filter))
                filter = "*.dll";
            else if (!filter.EndsWith("*.dll"))
                throw new ArgumentException("invalid filter. the filter format should like be *.dll.");
            if (Directory.Exists(directory))
            {
                var files = Directory.GetFiles(directory, filter);
                if (files != null && files.Length > 0)
                    foreach (string file in files)
                        LoadAssembly(file);

                if (!includeChildDirectory) return;

                var dirs = Directory.GetDirectories(directory);
                if (dirs != null && dirs.Length > 0)
                    foreach (var item in dirs)
                        LoadAssemblies(item,filter,  includeChildDirectory);
            }
        }

        public Type LoadType(string fullTypeName)
        {
            var arr = fullTypeName.Trim().Split(',');
            var asmName = arr[1];
            var newTypeName = arr[0];

            if (string.IsNullOrEmpty(asmName))
                ThrowIfTypeNameNotExists(fullTypeName);

            return LoadType(asmName, newTypeName);
        }

        private static void ThrowIfTypeNameNotExists(String typeName)
        {
            String message = String.Format("The type name {0} could not be located", typeName);
            throw new Exception(message);
        }

        public Type LoadType(string assemblyName, string typeName)
        {
            try
            {
                var asms = AppDomain.CurrentDomain.GetAssemblies();
                Assembly asm = null;
                if (asms != null && asms.Length > 1)
                    asm = asms.FirstOrDefault((item) => string.Equals(item.GetName().Name, assemblyName, StringComparison.OrdinalIgnoreCase));
                if (asm == null)
                {
                    assemblyName = assemblyName + ".dll";
                    if (!File.Exists(assemblyName))
                        assemblyName = @"AddIns/" + assemblyName;
                    asm = Assembly.LoadFrom(assemblyName);
                }

                if (asm != null)
                    return asm.GetType(typeName);
                return null;
            }
            catch (Exception ex)
            {
                ex.RaiseException();
                return null;
            }
        }

        public object NewInstance(Type type, params object[] args)
        {
            try
            {
                Type[] signature;
                if (args == null || args.Length == 0)
                    signature = new Type[0];
                else
                    signature = Array.ConvertAll<object, Type>(args, (item) => item.GetType());

                ConstructorInfo info = type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, signature, null);
                if (info == null)
                {
                    string format = "Could not find a public constructor for the type {0}";
                    throw new Exception(string.Format(format, type));
                }

                object uninitializedObject = null;
                uninitializedObject = FormatterServices.GetUninitializedObject(type); 

                info.Invoke(uninitializedObject, args);
                return uninitializedObject;
            }
            catch (Exception ex)
            {
                ex.RaiseException();
                return null;
            }
        }

        public T NewInstance<T>(Type type, params object[] args)
        {
            return (T)NewInstance(type, args);
        }

        public T NewInstance<T>(params object[] args)
        {
            return (T)NewInstance(typeof(T), args);
        }

        public object NewInstance(string fullTypeName, params object[] args)
        {
            Type type = LoadType(fullTypeName);
            if (type != null)
                return NewInstance(type, args);
            return null;
        }

        public T NewInstance<T>(string fullTypeName, params object[] args)
        {
            return (T)NewInstance(fullTypeName, args);
        }

        public object Invoke(Delegate method, params object[] args)
        {
            return method.DynamicInvoke(args);
        }

        public void Dispose() { }
    }
}
